/*
 * SteepTimer - A timer designed for tea fanatics
 * Copyright (C) 2011  Wesley Crossman
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "ui_TeaWindow.h"
#include "TeaWindow.h"
#include "TeaTimerEditDialog.h"
#include "TeaOptionsDialog.h"
#include "TeaAboutDialog.h"
#include "TeaAlarmDialog.h"
#include "TeaIcon.h"
#include <Qt>
#include <QAction>
#include <QCloseEvent>
#include <QDebug>
#include <QDesktopServices>
#include <QFileDialog>
#include <QInputDialog>
#include <QLabel>
#include <QListWidgetItem>
#include <QMenu>
#include <QMessageBox>
#include <QPixmap>
#include <QSharedPointer>
#include <QSettings>
#include <QTimer>
#include <QUrl>
#include <QSharedMemory>

const char *TeaWindow::NEW_GROUP_COMBO = QT_TRANSLATE_NOOP("TeaWindow", "- New -");


GroupEntry::GroupEntry(QObject *parent) : QObject(parent) {
}

TeaWindow::TeaWindow(QWidget *parent) :
            QMainWindow(parent, Qt::Window|Qt::WindowContextHelpButtonHint|Qt::WindowMinimizeButtonHint),
            m_ui(new Ui::TeaWindow),
            m_timerGroupsModified(false),
            m_startMinimized(false) {
    //** base window setup
    m_ui->setupUi(this);
    connect( m_ui->lstTimers, SIGNAL(runningTimerIndexChanged(int)),
             SLOT(updateUi()) );
    connect( qApp, SIGNAL(aboutToQuit()),
             SLOT(aboutToQuitEvent()) );

    //** update consolidation timer
    m_updateUiTimer = new QTimer(this);
    m_updateUiTimer->setInterval(0);
    m_updateUiTimer->setSingleShot(true);
    connect( m_updateUiTimer, SIGNAL(timeout()),
             SLOT(updateUi()) );
    connect( m_ui->lstTimers, SIGNAL(currentRowChanged(int)),
             SLOT(updateUi()) );

    //** alarm check timer
    m_clockCheckTimer = new QTimer(this);
    m_clockCheckTimer->setInterval(750);
    connect( m_clockCheckTimer, SIGNAL(timeout()),
             SLOT(timerTimeout()) );

    //** load program settings
    QSettings settings;
    //load the default settings from file and set any that aren't set already
    QSettings defaults(":/resources/defaults.ini", QSettings::IniFormat);
    foreach (QString key, defaults.allKeys()) {
        if (settings.value(key).isNull()) {
            settings.setValue(key, defaults.value(key));
        }
    }

    //** status bar
    m_lblStatusTimeLeft = new QLabel(this);
    statusBar()->addPermanentWidget(m_lblStatusTimeLeft);

    //** tray icon
    m_iconSettings = new TeaIconSettings();
    m_trayIcon = new QSystemTrayIcon(this);
    m_trayIcon->setToolTip(tr("SteepTimer")); //also set in updateUi
    m_trayMenu = new QMenu(this);
    m_trayMenu->addAction( tr("&Show"), this, SLOT(show()) );
    m_trayMenu->addSeparator();
    m_aStartNext = m_trayMenu->addAction( tr("&Start Next"), this, SLOT(startNextTimer()) );
    m_aStopTimer = m_trayMenu->addAction( tr("&Stop Timer"), this, SLOT(stopTimer()) );
    m_trayMenu->addSeparator();
    m_trayMenu->addAction( tr("E&xit"), qApp, SLOT(quit()) );

    connect( m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
             SLOT(trayActivated(QSystemTrayIcon::ActivationReason)) );
    m_trayIcon->setContextMenu(m_trayMenu);

    //** schedule additional loading actions
    QTimer::singleShot( 0, this, SLOT(lazyLoad()) );
}

TeaWindow::~TeaWindow() {
    QSettings settings;
    settings.setValue("lastUsed", m_ui->cmbGroupNames->currentText());
    delete m_ui; m_ui = NULL;
    delete m_iconSettings; m_iconSettings = NULL;
}

void TeaWindow::lazyLoad() {
    QSettings settings;
    loadGroups(&settings);
    populateComboBox();
    int index = m_ui->cmbGroupNames->findText(settings.value("lastUsed").toString());
    if (index >= 0) {
        m_ui->cmbGroupNames->setCurrentIndex(index);
    }

    updateUi();

    m_singleProcessMem = new QSharedMemory("Crossman.Wesley.SteepTimer", this);
    if (m_singleProcessMem->attach() || !m_singleProcessMem->create(sizeof(int))) {
        m_singleProcessMem->lock();
        ((int *)m_singleProcessMem->data())[0] = 1;
        m_singleProcessMem->unlock();
        QApplication::exit();
        return;
    }
    m_singleProcessTimer = new QTimer(this);
    connect( m_singleProcessTimer, SIGNAL(timeout()), SLOT(singleProcessTimeout()) );
    m_singleProcessTimer->start(2000);

    if (m_startMinimized) {
        if (QSystemTrayIcon::isSystemTrayAvailable() && isTrayIconSet()) {
            setInTray(true);
        } else {
            showMinimized();
        }
    } else {
        show();
    }
}

void TeaWindow::loadGroups(QSettings *settings) {
    settings->beginGroup("groups");
    QStringList keys = settings->childGroups();
    for (int a=0;a<keys.size();++a) {
        int size = settings->beginReadArray(keys[a]);
        QList < QTime > timers;
        for (int b=0;b<size;++b) {
            settings->setArrayIndex(b);
            timers.append(settings->value("time").toTime());
        }
        settings->endArray();
        settings->beginGroup(keys[a]);
        GroupEntry *entry = new GroupEntry(this);
        entry->m_title = keys[a];
        entry->m_reminderNote = settings->value("reminderNote", "").toString();
        entry->m_timers = timers;
        if (m_timerGroups.contains(keys[a])) {
            delete m_timerGroups[keys[a]]; m_timerGroups.remove(keys[a]);
        }
        m_timerGroups[keys[a]] = entry;
        settings->endGroup();
    }
    settings->endGroup();
}

void TeaWindow::populateComboBox() {
    m_ui->cmbGroupNames->clear();
    m_ui->cmbGroupNames->addItem(tr(NEW_GROUP_COMBO));
    QList < QString > keys(m_timerGroups.keys());
    qStableSort(keys);
    for (int a=0;a<keys.size();++a) {
        m_ui->cmbGroupNames->addItem(keys[a]);
    }
}

void TeaWindow::saveGroups(QSettings *settings) {
    settings->beginGroup("groups");
    settings->remove(""); //remove all
    QList < QString > keys(m_timerGroups.keys());
    for (int a=0;a<keys.size();++a) {
        settings->beginWriteArray(keys[a]);
        GroupEntry *entry = m_timerGroups[keys[a]];
        for (int b=0;b<entry->m_timers.size();++b) {
            settings->setArrayIndex(b);
            settings->setValue("time", entry->m_timers[b]);
        }
        settings->endArray();
        settings->beginGroup(keys[a]);
        settings->setValue("reminderNote", entry->m_reminderNote);
        settings->endGroup();
    }
    settings->endGroup();
    m_timerGroupsModified = false;
}

void TeaWindow::updateUi() {
    bool running = m_clockCheckTimer->isActive();
    bool entries = m_timers.size();
    int nextIndex = getNextTimerIndex();
    m_aStartNext->setText( (nextIndex == 0) ? tr("&Start Over") : tr("&Start Next") );
    m_aStartNext->setEnabled(entries);
    m_aStopTimer->setEnabled(running);
    m_ui->btnStartStop->setEnabled(entries);
    m_ui->btnSaveGroup->setEnabled(entries);
    m_ui->btnDelGroup->setEnabled(m_ui->cmbGroupNames->currentText() != tr(NEW_GROUP_COMBO));
    m_ui->btnEditTimer->setEnabled(m_ui->lstTimers->currentIndex().isValid());
    if (running) {
        m_ui->btnStartStop->setText(tr("&Stop Timer"));
    } else {
        m_ui->btnStartStop->setText(tr("&Start Next Timer"));
    }

    m_iconSettings->m_running = running;
    QPixmap pixmap = TeaIcon::instance()->generateIcon(*m_iconSettings);
    m_trayIcon->setIcon(QIcon(pixmap));
    setInTray(!isVisible()); //updates tray icon visibility

    if (!m_clockCheckTimer->isActive() || m_elapsed.elapsed() > getCurrentMsecs()) {
        m_lblStatusTimeLeft->setText(tr(""));
        m_trayIcon->setToolTip(tr("SteepTimer"));
    } else {
        int secs = getRemainingSecs();
        QString msg;
        if (secs > 60) {
            msg = tr("%1:%2 left").arg(secs/60).arg(secs%60, 2, 10, QChar('0'));
        } else {
            msg = tr("%1 secs").arg(secs);
        }
        m_lblStatusTimeLeft->setText(msg);
        m_trayIcon->setToolTip(tr("SteepTimer\n%1", "tray icon tooltip").arg(msg));
    }
}

bool TeaWindow::isTrayIconSet() {
    QSettings settings;
    return settings.value("settings/useTrayIcon").toBool();
}

void TeaWindow::setInTray(bool value) {
    m_trayIcon->setVisible( value || isTrayIconSet() );
    setVisible(!value);
}

int TeaWindow::getCurrentIndex() {
    return m_ui->lstTimers->getRunningTimerIndex();
}

QTime TeaWindow::getCurrentTimer() {
    return (getCurrentIndex() != -1) ? m_timers[getCurrentIndex()] : QTime();
}

int TeaWindow::getNextTimerIndex() {
    if (m_timers.size() && m_ui->lstTimers->getRunningTimerIndex() > -1) {
        return (m_ui->lstTimers->getRunningTimerIndex()+1) % m_timers.size();
    } else if (m_timers.size()) {
        return (m_ui->lstTimers->getLastTimerIndex()+1) % m_timers.size();
    } else {
        return -1;
    }
}

int TeaWindow::getCurrentSecs() {
    return QTime(0,0).secsTo(getCurrentTimer());
}

int TeaWindow::getCurrentMsecs() {
    return QTime(0,0).msecsTo(getCurrentTimer());
}

int TeaWindow::getRemainingSecs() {
    return getCurrentSecs() - int(m_elapsed.elapsed()/1000);
}

void TeaWindow::stopTimer() {
    m_clockCheckTimer->stop();
    m_ui->lstTimers->deselectRunningTimer();
    m_updateUiTimer->start();
}

void TeaWindow::startTimerByIndex(int newIndex) {
    m_clockCheckTimer->start();
    m_elapsed.restart();
    m_ui->lstTimers->selectTimer(newIndex);
    m_updateUiTimer->start();
}

void TeaWindow::startNextTimer() {
    stopTimer();
    int newIndex = (m_ui->lstTimers->getLastTimerIndex()+1) % m_timers.size();
    startTimerByIndex(newIndex);
    m_updateUiTimer->start();
}

QListWidgetItem *TeaWindow::addToList(int index) {
    QListWidgetItem *widgetItem = new QListWidgetItem(m_ui->lstTimers);
    widgetItem->setText(m_timers[index].toString("mm:ss"));
    widgetItem->setData(Qt::UserRole, m_timers[index]);
    m_ui->lstTimers->addItem(widgetItem);
    m_updateUiTimer->start();
    m_ui->lstTimers->updateTimerFonts();
    return widgetItem;
}

void TeaWindow::repopulateList() {
    stopTimer();
    m_ui->lstTimers->resetLastTimerIndex();
    m_ui->lstTimers->clear();
    for (int a=0;a<m_timers.size();++a) {
        addToList(a);
    }
    m_updateUiTimer->start();
}

void TeaWindow::showEditDialog(int row) {
    QTime time;
    if (row > -1) time = m_ui->lstTimers->item(row)->data(Qt::UserRole).toTime();
    QSharedPointer < TeaTimerEditDialog > edit(new TeaTimerEditDialog(this, time));
    if (edit->exec() == QDialog::Accepted) {
        QListWidgetItem *widgetItem = NULL;
        if (edit->getReturn() == TeaTimerEditDialog::Delete) {
            //delete existing timer
            stopTimer();
            m_timers.removeAt(row);
            repopulateList();
        } else if (row > -1) {
            //save existing timer
            widgetItem = m_ui->lstTimers->item(row);
            m_timers[row] = edit->getTime();
            m_ui->lstTimers->item(row)->setText(edit->getTime().toString("mm:ss"));
            m_ui->lstTimers->item(row)->setData(Qt::UserRole, edit->getTime());
        } else {
            //create new timer
            m_timers.append(edit->getTime());
            widgetItem = addToList(m_timers.size()-1);
        }
        if (edit->getReturn() == TeaTimerEditDialog::Run) {
            //now that we've saved, start the timer
            startTimerByIndex(m_ui->lstTimers->row(widgetItem));
        } else if (edit->getReturn() == TeaTimerEditDialog::Save
                   && m_ui->lstTimers->row(widgetItem) == getCurrentIndex()) {
            //now that we've changed the running timer, stop it
            stopTimer();
        }
    }
    m_updateUiTimer->start();
}

void TeaWindow::closeEvent(QCloseEvent *event) {
    static bool firstTimeClosed = true;
    if (QSystemTrayIcon::isSystemTrayAvailable()
                && isTrayIconSet()) {
        event->accept();
        setInTray(true);
        if (firstTimeClosed) {
            firstTimeClosed = false;
            m_trayIcon->showMessage(tr("Minimized to tray"),
                                    tr("SteepTimer has been minimized to the tray"),
                                    QSystemTrayIcon::Information,
                                    1000);
        }
    } else {
        event->accept();
        QApplication::exit();
    }
}

void TeaWindow::commitData(QSessionManager &sessionManager) {
    Q_UNUSED(sessionManager);
    QSettings settings;
    saveGroups(&settings);
}

void TeaWindow::trayActivated(QSystemTrayIcon::ActivationReason reason) {
    if (reason == QSystemTrayIcon::Trigger) {
        showNormal();
        activateWindow();
        raise();
        updateUi();
    }
}

void TeaWindow::singleProcessTimeout() {
    if ( ((int *)m_singleProcessMem->data())[0] ) {
        ((int *)m_singleProcessMem->data())[0] = 0;
        setInTray(false);
        showNormal();
        activateWindow();
        raise();
        setFocus();
        updateUi();
    }
}

void TeaWindow::timerTimeout() {
    if (m_elapsed.elapsed() > getCurrentMsecs()) {
        QSharedPointer < TeaAlarmDialog > alarm(new TeaAlarmDialog(getNextTimerIndex()));
        alarm->setWindowTitle( tr("%1 Alarm").arg(getCurrentTimer().toString("m:ss")) );
        stopTimer();
        alarm->exec();
        if (alarm->getReturn() == TeaAlarmDialog::StartNext) {
            startNextTimer();
        }
    }
    m_updateUiTimer->start();
}

void TeaWindow::aboutToQuitEvent() {
    if (m_timerGroupsModified) {
        QSettings settings;
        saveGroups(&settings);
    }
}

void TeaWindow::on_btnStartStop_clicked() {
    if (m_clockCheckTimer->isActive()) {
        stopTimer();
    } else {
        startNextTimer();
    }
    m_updateUiTimer->start();
}

void TeaWindow::on_btnNewTimer_clicked() {
    showEditDialog(-1);
}

void TeaWindow::on_btnEditTimer_clicked() {
    showEditDialog(m_ui->lstTimers->currentRow());
}

void TeaWindow::on_cmbGroupNames_currentIndexChanged(const QString &text) {
    if (text.isNull()) {
        //repopulating the list
    } else if (text == m_currentGroup
                && m_timerGroups[text]->m_timers == m_timers
                && m_timerGroups[text]->m_reminderNote == m_ui->txtReminderNote->toHtml()) {
        //already loaded
    } else if (text == tr(NEW_GROUP_COMBO)) {
        m_currentGroup = QString();
        m_timers = QList < QTime >();
        m_ui->txtReminderNote->clear();
        repopulateList();
    } else {
        m_currentGroup = text;
        m_timers = m_timerGroups[text]->m_timers;
        m_ui->txtReminderNote->setText(m_timerGroups[text]->m_reminderNote);
        repopulateList();
    }
}

void TeaWindow::on_btnDelGroup_clicked() {
    if (QMessageBox::question(this, tr("Delete Group"),
                              tr("Are you sure you want to delete this group?"),
                              QMessageBox::Yes|QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
        stopTimer();
        GroupEntry *entry = m_timerGroups[m_ui->cmbGroupNames->currentText()];
        m_timerGroups.remove(m_ui->cmbGroupNames->currentText());
        delete entry; entry = NULL;
        m_timerGroupsModified = true;
        m_ui->cmbGroupNames->removeItem(m_ui->cmbGroupNames->currentIndex());
        m_updateUiTimer->start();
    }
}

void TeaWindow::on_btnSaveGroup_clicked() {
    if (m_ui->cmbGroupNames->currentText() == tr(NEW_GROUP_COMBO)) {
        //save timers as new group
        QString name = QInputDialog::getText(this, tr("Timer Group"), tr("Enter a name for the new timer group:"));
        if (!name.isEmpty() && name != tr(NEW_GROUP_COMBO)) {
            GroupEntry *entry = new GroupEntry(this);
            entry->m_timers = m_timers;
            entry->m_reminderNote = m_ui->txtReminderNote->toHtml();
            m_timerGroups[name] = entry;
            m_timerGroupsModified = true;
            m_ui->cmbGroupNames->addItem(name);
            m_ui->cmbGroupNames->setCurrentIndex(m_ui->cmbGroupNames->findText(name, Qt::MatchExactly));
            m_currentGroup = name;
        }
    } else {
        //overwrite old timers
        m_timerGroups[m_currentGroup]->m_timers = m_timers;
        m_timerGroups[m_currentGroup]->m_reminderNote = m_ui->txtReminderNote->toHtml();
        m_timerGroupsModified = true;
    }
}

void TeaWindow::on_lstTimers_doubleClickedAnywhere(const QModelIndex &index) {
    showEditDialog(index.isValid() ? index.row() : -1);
}

void TeaWindow::on_aExit_triggered() {
    qApp->quit();
}

void TeaWindow::on_aSendToTray_triggered() {
    setInTray(true);
}

void TeaWindow::on_aImportTimerGroups_triggered() {
    QString filename = QFileDialog::getOpenFileName(this,
                "Import Timer Groups",
                QString(),
                tr("Timer group files (*.tmrgroups)"));
    if (!filename.isEmpty()) {
        QSettings settings(filename, QSettings::IniFormat);
        m_ui->cmbGroupNames->setCurrentIndex(0);
        loadGroups(&settings);
        populateComboBox();
        updateUi();
    }
}

void TeaWindow::on_aExportTimerGroups_triggered() {
    QString filename = QFileDialog::getSaveFileName(this,
                "Export Timer Groups",
                QString(),
                tr("Timer group files (*.tmrgroups)"));
    if (!filename.isEmpty()) {
        QSettings settings(filename, QSettings::IniFormat);
        saveGroups(&settings);
    }
}

void TeaWindow::on_aOptions_triggered() {
    QSharedPointer < TeaOptionsDialog > options(new TeaOptionsDialog(this));
    if (options->exec() == QDialog::Accepted) {
        options->saveSettings();
        m_iconSettings->loadFromSettings();
        updateUi();
    }
}

void TeaWindow::on_aContents_triggered() {
    QDesktopServices::openUrl(QUrl("http://code.google.com/p/steeptimer/wiki/UsingSteepTimer"));
}

void TeaWindow::on_aWebsite_triggered() {
    QDesktopServices::openUrl(QUrl("http://code.google.com/p/steeptimer/"));
}

void TeaWindow::on_aAbout_triggered() {
    QSharedPointer < TeaAboutDialog > options(new TeaAboutDialog(this));
    options->exec();
}
